package cn.likole.oj.module;

import cn.likole.oj.bean.*;
import cn.likole.oj.service.EmailService;
import cn.likole.oj.utils.Toolkit;
import org.nutz.aop.interceptor.async.Async;
import org.nutz.dao.*;
import org.nutz.dao.sql.Sql;
import org.nutz.dao.util.Daos;
import org.nutz.img.Images;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.lang.Strings;
import org.nutz.lang.random.R;
import org.nutz.lang.util.NutMap;
import org.nutz.mvc.Mvcs;
import org.nutz.mvc.annotation.*;
import org.nutz.mvc.filter.CheckSession;
import org.nutz.mvc.impl.AdaptorErrorContext;
import org.nutz.mvc.upload.TempFile;
import org.nutz.mvc.upload.UploadAdaptor;
import org.nutz.plugins.apidoc.annotation.Api;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.sql.SQLException;
import java.util.Date;
import java.util.List;

/**
 * Created by likole on 7/24/18.
 */
@IocBean
@At("/user")
@Filters(@By(type = CheckSession.class, args = {"uid", "/"}))
@Api(name = "用户模块")
public class UserModule {

    @Inject
    protected Dao dao;

    @Inject protected EmailService emailService;

    protected byte[] emailKEY = R.sg(24).next().getBytes();

    /**
     * 检查邮箱和密码格式
     *
     * @param user 用户
     * @return 错误信息
     */
    private String checkEmailAndPassword(User user) {
        //验证邮箱
        String EMAIL_REGEX = "^[\\w-_\\.+]*[\\w-_\\.]\\@([\\w]+\\.)+[\\w]+[\\w]$";
        String email = user.getEmail();
        if (email == null) return "Email地址不能为空";
        if (!email.matches(EMAIL_REGEX)) return "Email格式错误";

        //验证密码
        String password = user.getPassword();
        if (password == null) return "密码不能为空";

        return null;
    }

    @Async
    private void checkAddr(int userId){
        NutMap re=new NutMap();
        List<LoginLog> lists=dao.query(LoginLog.class,Cnd.where("userId","=",userId).desc("time"),dao.createPager(1,2));
        String ip1=null,ip2=null;
        if(lists.size()>=1)
            ip1=lists.get(0).getIp();
        if(lists.size()>=2)
            ip2=lists.get(1).getIp();
        Address addr1=Toolkit.TaoBaoIP(ip1);
        Address addr2=Toolkit.TaoBaoIP(ip2);
        if(addr1!=null&&addr2!=null&&addr1.getCity()!=null&& addr2.getCity()!=null){
            if(!addr1.getCity().equals(addr2.getCity())){
                Mail mail=new Mail();
                mail.setToUser(userId);
                mail.setFromUser(-1);
                mail.setNew_mail(true);
                mail.setTitle("异地登录提醒");
                String content="上次登录地址:"+ip2+"("+addr2.getCountry()+"-"+addr2.getRegion()+"-"+addr2.getCity()
                        +")\n本次登录地址:"+ip2+"("+addr1.getCountry()+"-"+addr1.getRegion()+"-"+addr1.getCity()+")";
                mail.setContent(content);
                mail.setIn_date(new Date());
                dao.insert(mail);
            }
        }
    }

    @At
    @Filters
    @Api(name = "检查邮箱",description = "检查邮箱是否已被注册")
    public Object checkEmail(@Param("email") String email) {
        NutMap re = new NutMap();
        List<User> users = dao.query(User.class, Cnd.where("email", "=", email));
        if (users.size() != 0) return re.setv("ok", false).setv("msg", "该邮箱已被注册").setv("valid", false);
        return re.setv("ok", true).setv("valid", true);
    }

    /**
     * 检查邮箱是否已被注册
     * @param nickname
     * @return
     */
    @At
    @Filters
    public Object checkNickname(@Param("nickname") String nickname) {
        NutMap re = new NutMap();
        List<User> users = dao.query(User.class, Cnd.where("nickname", "=", nickname));
        if (users.size() != 0) return re.setv("ok", false).setv("msg", "该昵称已被使用").setv("valid", false);
        return re.setv("ok", true).setv("valid", true);
    }

    @At
    @POST
    @Filters
    public Object register(@Param("src/main") User user, @Param("emailkey")String emailkey, HttpServletRequest request, HttpSession session) {
        NutMap re = new NutMap();

        //检查邮箱密码格式
        String msg = checkEmailAndPassword(user);
        if (msg != null) return re.setv("ok", false).setv("msg", msg);

        //检查昵称格式
        if (user.getNickname() == null || user.getNickname().length() < 3)
            return re.setv("ok", false).setv("msg", "昵称至少为三个字");

        //检查邮箱和昵称是否重复
        List<User> users = dao.query(User.class, Cnd.where("email", "=", user.getEmail()).or("nickname", "=", user.getNickname()));
        if (users.size() != 0) return re.setv("ok", false).setv("msg", "该邮箱或昵称已被注册");

        //检查邮箱验证码是否一致
        if(!emailkey.equals(session.getAttribute("emailkey")))return re.setv("ok",false).setv("msg","验证码错误");
        if(!user.getEmail().equals(session.getAttribute("email")))return re.setv("ok",false).setv("msg","验证邮箱不匹配");


        //注册
        user.setMoney(0);
        user.setState(0);
        user.setTutorialId(1);
        user.setRegisterTime(new Date());
        dao.insert(user);

        //登录操作
        doLog(user.getPassword(),user,request);
        setSession(user,session);
        session.removeAttribute("email");
        session.removeAttribute("emailkey");

        return re.setv("ok", true);
    }

    /**
     * 记录用户的登录信息
     * @param input 用户填写的密码
     * @param user 正确信息
     * @param request
     */
    private void doLog(String input,User user, HttpServletRequest request){
        LoginLog loginLog=new LoginLog();
        loginLog.setIp(request.getRemoteAddr());
        loginLog.setTime(new Date());
        loginLog.setUserId(user.getId());
        loginLog.setPassword(input);
        loginLog.setOk(user.getPassword().equals(input));
        dao.fastInsert(loginLog);
    }

    /**
     * 设置session，注册登录时使用
     * @param user 用户
     * @param session
     */
    private void setSession(User user,HttpSession session){
        session.setAttribute("uid", user.getId());
        session.setAttribute("nickname", user.getNickname());
        session.removeAttribute("state0");
        session.removeAttribute("state1");
        session.setAttribute("state" + user.getState(), 1);
    }

    @At
    @POST
    @Filters
    public Object login(@Param("src/main") User user, HttpServletRequest request, HttpSession session) {
        NutMap re = new NutMap();

        //检查邮箱密码格式
        String msg = checkEmailAndPassword(user);
        if (msg != null) return re.setv("ok", false).setv("msg", msg);

        //检查用户名密码,正确信息为users.get(0)
        User right=dao.fetch(User.class,user.getEmail());
        if(right==null)  return re.setv("ok", false).setv("msg", "用户不存在");
        doLog(user.getPassword(),right,request);//登录日志
        if (!right.getPassword().equals(user.getPassword())) {
            return re.setv("ok", false).setv("msg", "用户名或密码错误");
        }

        //登陆
        setSession(right,session);
        try{
            checkAddr(right.getId());
        }catch (Exception e){
            //应该不会发生
        }

        return re.setv("ok", true);
    }



    @At
    @Ok("re")
    @Filters
    public String loginPage(HttpSession session) {
        if (session.getAttribute("uid") != null) return ">>:/";
        return "jsp:jsp.loginpage";
    }

    @At
    @Ok(">>:/user/loginpage")
    @Filters
    public void logout(HttpSession session) {
        session.invalidate();
    }


    @AdaptBy(type = UploadAdaptor.class, args = {"${app.root}/WEB-INF/tmp/user_avatar", "8192", "utf-8", "20000", "1024000"})
    @POST
    @At("/avatar")
    public Object uploadAvatar(@Param("file") TempFile tf, HttpSession session, AdaptorErrorContext err) {
        NutMap re = new NutMap();
        String msg = null;
        if (err != null && err.getAdaptorErr() != null) {
            return re.setv("ok",false).setv("smg","上传文件大小不符合规定");
        } else if (tf == null) {
            return re.setv("ok",false).setv("smg","空文件");
        } else {
            User user = dao.fetch(User.class, (int) session.getAttribute("uid"));
            try {
                BufferedImage image = Images.read(tf.getFile());
                //缩放
                image = Images.zoomScale(image, 128, 128, Color.WHITE);
                ByteArrayOutputStream out = new ByteArrayOutputStream();
                /*
                 * quality:图片输出质量
                 */
                Images.writeJpeg(image, out, 0.9f);
                user.setAvatar(out.toByteArray());
                dao.update(user, "^avatar$");
            } catch (DaoException e) {
                return re.setv("ok",false).setv("smg","系统错误");
            } catch (Throwable e) {
                return re.setv("ok",false).setv("smg","图片格式错误");
            }
        }
        if (msg != null) Mvcs.getHttpSession().setAttribute("upload-error-msg", msg);
        return re.setv("ok", true).setv("msg", "上传成功");
    }

    @At("/avatar")
    @Ok("raw:jpg")
    @GET
    public Object getAvatar(@Param("uid") int id, HttpSession session) throws SQLException {
        User user;
        if (id == 0)
            user = Daos.ext(dao, FieldFilter.create(User.class, "^avatar$")).fetch(User.class, (int)session.getAttribute("uid"));
        else
            user = Daos.ext(dao, FieldFilter.create(User.class, "^avatar$")).fetch(User.class, id);
        if (user == null || user.getAvatar() == null) {
            return new File(session.getServletContext().getRealPath("/assets/img/dAvatar.jpg"));
        }
        return user.getAvatar();
    }

    @At
    @Ok("json:{locked:'password|infoId'}")
    public User basicInfo(HttpSession session) {
        User user = dao.fetch(User.class, (int) session.getAttribute("uid"));
        dao.fetchLinks(user, "tutorial");
        dao.fetchLinks(user, "userInfo");
        return user;
    }

    /*
     * 更新信息部分
     */
    @At
    @POST
    public Object updatePassword(HttpSession session, @Param("password_old") String oldPassword, @Param("password_new") String newPassword) {
        NutMap re = new NutMap();
        if ("".equals(newPassword)) return re.setv("ok", false).setv("msg", "密码不能为空");

        User user = dao.fetch(User.class, (int) session.getAttribute("uid"));
        if (!user.getPassword().equals(oldPassword))
            return re.setv("ok", false).setv("msg","原密码错误");
        user.setPassword(newPassword);
        dao.update(user, "^password$");
        return re.setv("ok", true);
    }

    @At
    public Object updateNickname(HttpSession session, @Param("nickname") String nickname) {
        User user = dao.fetch(User.class, (int) session.getAttribute("uid"));
        user.setNickname(nickname);
        //todo:改名卡
        dao.update(user, "^nickname$");
        NutMap re = new NutMap();
        return re.setv("ok", true);
    }

    @At
    public Object updateQq(HttpSession session, @Param("qq") String qq) {
        User user = dao.fetch(User.class, (int) session.getAttribute("uid"));
        user.setQq(qq);
        dao.update(user, "^qq$");
        NutMap re = new NutMap();
        return re.setv("ok", true);
    }

    @At
    public Object updateSid(HttpSession session, @Param("sid") String sid) {
        NutMap re = new NutMap();
        User user = dao.fetch(User.class, (int) session.getAttribute("uid"));
        user.setSid(sid);
        int rows=dao.update(user, "^sid$");
        if(rows==0)
            return re.setv("ok",false);
        return re.setv("ok", true);
    }

//    @At
//    public Object addMoney(@Param("uid")int id,@Param("money")int money,int num){
//        User user= dao.fetch(User.class,id);
//        user.setMoney(money+num);
//        dao.update(user,"^money$");
//        NutMap re=new NutMap();
//        return re.setv("ok",true);
//    }
//
//    @At
//    public Object decMoney(@Param("uid")int id,@Param("money")int money,int num){
//        User user= dao.fetch(User.class,id);
//        user.setMoney(money-num);
//        dao.update(user,"^money$");
//        NutMap re=new NutMap();
//        return re.setv("ok",true);
//    }


    @At
    public int getMoney(HttpSession session){
        return dao.fetch(User.class,(int)session.getAttribute("uid")).getMoney();
    }

    @At
    @POST
    @Filters
    public Object getEmailKey(@Param("src/main") User user, HttpSession session, HttpServletRequest req){
        NutMap re=new NutMap();
//        String token=String.format("%s%s%s",user.getId(),user.getEmail(),System.currentTimeMillis());
//        token = Toolkit._3DES_encode(emailKEY, token.getBytes());
//        String url = req.getRequestURL() + "?token=" + token;
        String html = "您的验证码为：%s";
        String key=Toolkit.getStringRandom(6);
        html = String.format(html, key);
        try {
            boolean ok = emailService.send(user.getEmail(), "check email by oj:", html);
            if (!ok) {
                return re.setv("ok", false).setv("msg", "发送失败");
            }
        } catch (Throwable e) {
            e.printStackTrace();
            return re.setv("ok", false).setv("msg", "发送失败");
        }
        session.setAttribute("email",user.getEmail());
        session.setAttribute("emailkey",key);
        return re.setv("ok", true);
    }

//    @At
//    @GET
//    public Object activateEmail(@Param("token")String token,HttpSession session){
//        NutMap re=new NutMap();
//        if(Strings.isBlank(token))
//            return re.setv("ok",false).setv("mag","请不要直接访问这个链接");
//        if(token.length()<10)
//            return re.setv("ok",false).setv("mag","非法token");
//        token=Toolkit._3DES_decode(emailKEY,Toolkit.hexstr2bytearray(token));
//        if(token==null)
//            return re.setv("ok",false).setv("mag","非法token");
//        String[] tmp=token.split(",",3);
//        if(tmp.length!=3||tmp[0].length()==0||tmp[1].length()==0||tmp[3].length()==0)
//            return re.setv("ok",false).setv("mag","非法token");
//        long time=Long.parseLong(tmp[2]);
//        if(System.currentTimeMillis()-time>10*60*1000)
//            return re.setv("ok",false).setv("mag","该验证链接已超时");
//        int id=Integer.parseInt(tmp[0]);
//        User user=dao.fetch(User.class,Cnd.where("id","=",id).and("email","=",tmp[1]));
//        user.setValid(1);
//        int rows=dao.update(user);
//        if(rows==1)
//            return re.setv("ok",false).setv("mag","验证成功");
//        return re.setv("ok",false).setv("mag","验证失败!请重新验证!");
//    }

    @At
    public Object lossPassword(HttpSession session,@Param("email")String email,@Param("newpwd")String pwd,@Param("emailkey")String emailkey){
        NutMap re=new NutMap();
        //检查邮箱验证码是否一致
        if(!emailkey.equals(session.getAttribute("emailkey")))return re.setv("ok",false).setv("msg","验证码错误");
        if(!email.equals(session.getAttribute("email")))return re.setv("ok",false).setv("msg","验证邮箱不匹配");
        User user=dao.fetch(User.class,email);
        user.setPassword(pwd);
        int rows=dao.update(user);
        if(rows==0)return re.setv("ok",false).setv("msg","重置失败请重试");
        return re.setv("ok",true);
    }

    @At
    @Ok("jsp:jsp.profile")
    public void profile(){
    }

    @At
    public Object moneyLog(@Param("search[value]") String q, @Param("src/main")Datatables datatables, HttpSession session){
        NutMap re=new NutMap();
        Sql sql;
        int recordsFiltered;
        int recordsTotal=dao.func(MoneyLog.class,"count","1");
        if(Strings.isBlank(datatables.getSearch())){
            sql= Sqls.create("select reason,change_log,time from money_log where userId=@uid order by time desc limit @start,@length");
            sql.setParam("uid",session.getAttribute("uid")).setParam("start",datatables.getStart()).setParam("length",datatables.getLength());
            recordsFiltered=recordsTotal;
        }else{
            sql= Sqls.create("select reason,change_log,time from money_log where userId=@uid and reason like  @search order by time desc limit @start,@length");
            sql.setParam("uid",session.getAttribute("uid")).setParam("start",datatables.getStart()).setParam("length",datatables.getLength()).setParam("search","%"+datatables.getSearch()+"%");
            recordsFiltered=dao.func(MoneyLog.class,"count","1",Cnd.where("reason","like","%"+datatables.getSearch()+"%"));
        }
        sql.setCallback(Sqls.callback.records());
        dao.execute(sql);
        return re.setv("draw",datatables.getDraw()).setv("recordsTotal",recordsTotal).setv("recordsFiltered",recordsFiltered).setv("data",sql.getResult());
    }

}